// SPDX-License-Identifier: GPL-2.0
/*
 * lombo_thermal.c - Lombo Thermal Management Implementation
 *
 * Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/cpu_cooling.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/thermal.h>
#include <linux/io.h>
#include <linux/delay.h>
#include "../../../../drivers/thermal/thermal_core.h"
#include "lombo_thermal.h"

static const struct of_device_id lombo_thermal_match[] = {
	{.compatible = "lombotech,lombo-thermal", },
	{},
};
MODULE_DEVICE_TABLE(of, lombo_thermal_match);

static int lombo_sys_get_temp(void *_data, int *temp)
{
	struct lombo_sensor_data *data = _data;

	*temp = lombo_get_temp(data->sensor_id, data->slope, data->offset);

	return 0;
}

static const struct thermal_zone_of_device_ops lombo_of_thermal_ops = {
	.get_temp = lombo_sys_get_temp,
};

static int lombo_get_sensor_id(struct device_node *np)
{
	int ret, id;
	struct of_phandle_args sensor_specs;

	if (!np) {
		PRT_ERR("np is null\n");
		return -ENODEV;
	}

	ret = of_parse_phandle_with_args(np, "thermal-sensors",
			"#thermal-sensor-cells",
			0, &sensor_specs);
	if (ret)
		PRT_ERR("parse thermal-sensors for id failed\n");

	if (sensor_specs.args_count >= 1) {
		id = sensor_specs.args[0];
		WARN(sensor_specs.args_count > 1,
				"%s: too many cells in sensor specifier %d\n",
				sensor_specs.np->name, sensor_specs.args_count);
	} else {
		id = 0;
	}

	of_node_put(sensor_specs.np);
	of_node_put(np);

	return id;
}

static int lombo_thermal_probe(struct platform_device *pdev)
{
	struct lombo_thermal_data *tdata = NULL;
	struct device_node *np = pdev->dev.of_node;
	struct device_node *tnp, *child;
	int ret = 0, zone_id = 0;
	int sensor_id[MAX_TSENSOR_NUM];
	const char *crit_mode;

	if (!np) {
		PRT_ERR("failed to get node\n");
		return -EINVAL;
	}

	tdata = devm_kzalloc(&pdev->dev, sizeof(*tdata), GFP_KERNEL);
	if (!tdata)
		return -ENOMEM;

	platform_set_drvdata(pdev, tdata);

	mutex_init(&tdata->th_lock);
	mutex_lock(&tdata->th_lock);

	tdata->pdev = pdev;

	ret = of_property_read_string(pdev->dev.of_node,
					"critical-mode", &crit_mode);

	tnp = of_find_node_by_name(NULL, "thermal-zones");
	if (!tnp)
		return -ENODEV;

	for_each_available_child_of_node(tnp, child) {
		tdata->sensor_data[zone_id].sensor_id =
					lombo_get_sensor_id(child);
		sensor_id[zone_id] = tdata->sensor_data[zone_id].sensor_id;
		if (sensor_id[zone_id] < 0) {
			dev_err(&pdev->dev, "Failed to get sensor id\n");
			ret = -ENODEV;
			goto out_unlock;
		}

		if (ret || !strcmp(crit_mode, "reboot"))
			tdata->sensor_data[zone_id].crit_mode = 1;
		else
			tdata->sensor_data[zone_id].crit_mode = 0;

		tdata->sensor_data[zone_id].offset =
				lombo_calibration_offset(sensor_id[zone_id]);
		tdata->sensor_data[zone_id].slope =
				lombo_get_slope(sensor_id[zone_id]);

		tdata->sensor_data[zone_id].tzd =
				devm_thermal_zone_of_sensor_register(&pdev->dev,
					sensor_id[zone_id],
					&tdata->sensor_data[zone_id],
					&lombo_of_thermal_ops);
		if (IS_ERR(tdata->sensor_data[zone_id].tzd)) {
			ret = PTR_ERR(tdata->sensor_data[zone_id].tzd);
			PRT_ERR("Failed to register thermal zone device %d\n",
				ret);
			goto out_unlock;
		}

		zone_id++;
		tdata->sensor_num = zone_id;
		PRT_DBG("Thermal zone device %d registered.\n",
			sensor_id[zone_id]);
	}

	mutex_unlock(&tdata->th_lock);
	return 0;

out_unlock:
	platform_set_drvdata(pdev, NULL);
	mutex_unlock(&tdata->th_lock);

	return ret;
}

static int lombo_thermal_remove(struct platform_device *pdev)
{
	struct lombo_thermal_data *tdata = platform_get_drvdata(pdev);

	platform_set_drvdata(pdev, NULL);
	mutex_destroy(&tdata->th_lock);

	return 0;
}

static int lombo_thermal_suspend(struct platform_device *pdev,
		pm_message_t state)
{
	struct lombo_thermal_data *tdata = platform_get_drvdata(pdev);
	int i;

	for (i = 0; i < tdata->sensor_num; i++)
		cancel_delayed_work(&tdata->sensor_data[i].tzd->poll_queue);

	return 0;
}

static int lombo_thermal_resume(struct platform_device *pdev)
{
	struct lombo_thermal_data *tdata = platform_get_drvdata(pdev);
	int delay, i;

	for (i = 0; i < tdata->sensor_num; i++) {
		delay = tdata->sensor_data[i].tzd->polling_delay;
		if (delay > 1000)
			mod_delayed_work(system_freezable_wq,
				&tdata->sensor_data[i].tzd->poll_queue,
				round_jiffies(msecs_to_jiffies(delay)));
		else if (delay)
			mod_delayed_work(system_freezable_wq,
				&tdata->sensor_data[i].tzd->poll_queue,
				msecs_to_jiffies(delay));
	}

	return 0;
}

static struct platform_driver lombo_thermal_driver = {
	.driver = {
		.owner = THIS_MODULE,
		.name = "lombo-thermal",
		.of_match_table = of_match_ptr(lombo_thermal_match),
	},
	.probe = lombo_thermal_probe,
	.suspend = lombo_thermal_suspend,
	.resume = lombo_thermal_resume,
	.remove = lombo_thermal_remove,
};

static int __init lombo_thermal_init(void)
{
	return platform_driver_register(&lombo_thermal_driver);
}

/* Should be later than gpadc_init */
late_initcall(lombo_thermal_init);

MODULE_AUTHOR("lomboswer@lombotech.com>");
MODULE_DESCRIPTION("Lombo thermal driver");
MODULE_LICENSE("GPL");
